/*
 * Copyright 2012 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid.cell.tripple;

import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import finbudgettable.client.ui.grid.Utils;
import finbudgettable.client.ui.grid.cell.AbstractEditableGridCell;

public class TrippleCell extends AbstractEditableGridCell<Boolean, Boolean> {

    interface Template extends SafeHtmlTemplates {
        @Template("<div class=\"{0}\"></div>")
        SafeHtml checkbox(String className);
    }

    public interface Resources extends ClientBundle {

        interface Style extends CssResource {
            @ClassName("b-checkbox-yes")
            String bCheckboxYes();

            @ClassName("b-checkbox-no")
            String bCheckboxNo();

            @ClassName("b-checkbox-yesno")
            String bCheckboxYesNo();

            @ClassName("b-tripple-cell")
            String bTrippleCell();
        }

        @Source("TrippleCell.css")
        Style style();

        @Source("checkbox_yes.png")
        DataResource checkboxYes();

        @Source("checkbox_no.png")
        DataResource checkboxNo();

        @Source("checkbox_yesno.png")
        DataResource checkboxYesNo();

        @Source("checkbox_yes.png")
        public ImageResource checkboxYesImg();

        @Source("checkbox_no.png")
        public ImageResource checkboxNoImg();

        @Source("checkbox_yesno.png")
        public ImageResource checkboxYesNoImg();
    }

    private static final Template TEMPLATE = GWT.create(Template.class);
    public static final Resources RESOURCES = GWT.create(Resources.class);
    private boolean readonly;
    private boolean parentReadonly;

    static {
        RESOURCES.style().ensureInjected();
    }

    public TrippleCell() {
        super(BrowserEvents.KEYDOWN, BrowserEvents.KEYPRESS, BrowserEvents.CLICK, BrowserEvents.DBLCLICK);
    }

    @Override
    public boolean dependsOnSelection() {
        return false;
    }

    @Override
    public boolean handlesSelection() {
        return false;
    }

    @Override
    public boolean isEditing(Context context, Element parent, Boolean value) {
        return false;
    }

    public String getCheckBoxClassName(Boolean isChecked) {
        if (isChecked == null) {
            return RESOURCES.style().bCheckboxYesNo();
        } else if (isChecked) {
            return RESOURCES.style().bCheckboxYes();
        } else {
            return RESOURCES.style().bCheckboxNo();
        }
    }

    @Override
    public void onBrowserEvent(Context context, Element parent, Boolean value,
                               NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
        if (isReadonly()) {
            return;
        }

        String type = event.getType();

        int keyCode = event.getKeyCode();
        boolean enterPressed =
                (BrowserEvents.KEYDOWN.equals(type) && keyCode == KeyCodes.KEY_ENTER)
                        || BrowserEvents.DBLCLICK.equals(type)
                        || (BrowserEvents.KEYPRESS.equals(type) && isCodePointIsSpace(event.getCharCode()))
                        || ((BrowserEvents.CLICK.equals(type)) && isEventForCheckBox(event, parent));
        if (enterPressed) {
            Element checkbox = parent.getFirstChild().cast();
            Boolean isChecked = getInputState(checkbox);

            /*
            * Toggle the value if the enter key was pressed and the cell handles
            * selection or doesn't depend on selection. If the cell depends on
            * selection but doesn't handle selection, then ignore the enter key and
            * let the SelectionEventManager determine which keys will trigger a
            * change.
            */
            if (handlesSelection() || !dependsOnSelection()) {
                Boolean newState =  getNextState(isChecked);
                checkbox.replaceClassName(getCheckBoxClassName(isChecked), getCheckBoxClassName(newState));
                if (valueUpdater != null) {
                    valueUpdater.update(newState);
                }
            }
        }
    }

    public Boolean getNextState(Boolean currentState) {
        if (currentState == null) {
            return Boolean.TRUE;
        } else if (currentState) {
            return Boolean.FALSE;
        } else {
            return null;
        }
    }

    private Boolean getInputState(Element checkbox) {
        Boolean isChecked;
        if (Utils.hasClassName(checkbox, RESOURCES.style().bCheckboxYes())) {
            isChecked = true;
        } else if (Utils.hasClassName(checkbox, RESOURCES.style().bCheckboxNo())) {
            isChecked = false;
        } else if (Utils.hasClassName(checkbox, RESOURCES.style().bCheckboxYesNo())) {
            isChecked = null;
        } else {
            throw new IllegalStateException("Cell is in incorrect state");
        }
        return isChecked;
    }

    private boolean isEventForCheckBox(NativeEvent event, Element parent) {
        Element checkbox = parent.getFirstChild().cast();
        return event.getEventTarget().equals(checkbox);
    }

    private boolean isCodePointIsSpace(int charCode) {
        return new String(Character.toChars(charCode)).equals(" ");
    }

    @Override
    public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
        sb.append(TEMPLATE.checkbox(getCheckBoxClassName(value)));
    }

    public boolean isReadonly() {
        return readonly || parentReadonly;
    }

    public void setReadonly(boolean readonly) {
        this.readonly = readonly;
    }

    public void setParentReadonly(boolean parentReadonly) {
        this.parentReadonly = parentReadonly;
    }
}
